/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 pjoc.pub, blademainer.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package pub.pjoc.pay.handler.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import pub.pjoc.pay.channels.handler.ChannelHandler;
import pub.pjoc.pay.dao.PayOrderDao;
import pub.pjoc.pay.event.manager.PayEventManager;
import pub.pjoc.pay.handler.PayHandler;
import pub.pjoc.pay.handler.vo.PayHandlerResult;
import pub.pjoc.pay.interceptor.InterceptResult;
import pub.pjoc.pay.interceptor.manager.InterceptorManager;
import pub.pjoc.pay.order.OrderGenerator;
import pub.pjoc.pay.pojo.PayOrder;
import pub.pjoc.pay.vo.PayRequest;
import pub.pjoc.pay.vo.PayResponse;

/**
 * Default pay handler.
 *
 * @author blademainer
 * @version 2016-09-08 22:30
 */
@Service
public class PayHandlerImpl implements PayHandler {
  private static final Logger logger = LoggerFactory.getLogger(PayHandlerImpl.class);
  private PayEventManager payEventManager;
  private InterceptorManager interceptorManager;
  private ChannelHandler channelHandler;
  private OrderGenerator orderGenerator;
  @Autowired
  private PayOrderDao payOrderDao;

  public PayHandlerImpl(PayEventManager payEventManager, InterceptorManager interceptorManager,
                        ChannelHandler channelHandler, OrderGenerator orderGenerator) {
    Assert.notNull(payEventManager, "PayEventManager must not be null!");
    Assert.notNull(interceptorManager, "InterceptorManager must not be null!");
    Assert.notNull(channelHandler, "ChannelHandler must not be null!");
    Assert.notNull(orderGenerator, "OrderGenerator must not be null!");
    this.payEventManager = payEventManager;
    this.interceptorManager = interceptorManager;
    this.channelHandler = channelHandler;
    this.orderGenerator = orderGenerator;
  }

  @Override
  public PayResponse handlePayRequest(PayRequest payRequest) {
    Assert.notNull(payRequest, "Pay request must not be null!");

    // notify pay request events.
    notifyRequested(payRequest);

    // invoke interceptors
    try {
      InterceptResult interceptResult = interceptorManager.intercept(payRequest);
      if (interceptResult.isSuccess()) {
        if (logger.isDebugEnabled()) {
          logger.debug("Passed intercepts. pay request: {}", payRequest);
        }
      } else {
        if (logger.isWarnEnabled()) {
          logger.warn("Intercepted pay request: {} with result: {}", payRequest, interceptResult);
        }
        // notify pay intercepted events.
        notifyIntercepted(payRequest, interceptResult);
        return InterceptResult.intercepted(interceptResult);
      }
    } catch (Exception e) {
      logger.error("Caught exception when invoke interceptors. Pay pay request: " + payRequest
          + ", error message: " + e.getMessage(), e);
    }

    // handle pay request.
    try {
      preProcessOrder(payRequest);
      PayResponse payResponse = channelHandler.handlePayRequest(payRequest);
      if (logger.isDebugEnabled()) {
        logger.debug("Channel handle pay request done. pay request: {}, result: {}",
            payRequest, payResponse);
      }
      return payResponse;
    } catch (Exception e) {
      logger.error("Caught exception when channel handle the pay request! ");
      return PayHandlerResult.SYSTEM_ERROR;
    }
  }

  private void preProcessOrder(PayRequest payRequest) {
    // generate order number.
    String orderNumber = orderGenerator.generateOrderNumber();
    payRequest.setOrderId(orderNumber);

    // save to db.
    PayOrder payOrder = new PayOrder();
    BeanUtils.copyProperties(payRequest, payOrder);

    payOrderDao.savePayOrder(payOrder);

  }

  private void notifyRequested(PayRequest payRequest) {
    try {
      payEventManager.notifyPayRequested(payRequest);
    } catch (Exception e) {
      logger.error("Notify pay requested event error with message: " + e.getMessage() + ", "
          + "payRequest: " + payRequest, e);
    }
  }

  private void notifyIntercepted(PayRequest request, InterceptResult interceptResult) {
    try {
      payEventManager.notifyPayRequestIntercepted(request, interceptResult);
    } catch (Exception e) {
      logger.error("Caught exception when notify InterceptEvents. Pay request: " + request
          + ", intercept result: " + interceptResult + ", error message: " + e.getMessage(), e);
    }
  }
}
